package com.yahoo.astra.fl.charts.axes
{
    import com.yahoo.astra.fl.charts.series.ISeries;
    import com.yahoo.astra.fl.charts.CartesianChart;
    import fl.core.UIComponent;
    import flash.text.TextFormat;	
    /**
     * An axis type representing a set of categories.
     * 
     * @author Josh Tynjala
     */
    public class CategoryAxis extends BaseAxis implements IAxis, IClusteringAxis
    {
        
    //--------------------------------------
    //  Constructor
    //--------------------------------------

        /**
         * Constructor.
         */
        public function CategoryAxis()
        {
        }
        
    //--------------------------------------
    //  Properties
    //--------------------------------------
        
        /**
         * @private
         * Used to determine the position of an item based on a value.
         */
        protected var categorySize:Number = 0;
        
        /**
         * @private
         * Storage for the categoryNames property.
         */
        private var _categoryNames:Array = [];
        
        /**
         * @private
         * Indicates whether the category labels are user-defined or generated by the axis.
         */
        private var _categoryNamesSetByUser:Boolean = false;
        
        /**
         * The category labels to display along the axis.
         */
        public function get categoryNames():Array
        {
            return this._categoryNames;
        }
        
        /**
         * @private
         */
        public function set categoryNames(value:Array):void
        {
            this._categoryNamesSetByUser = value != null && value.length > 0;
            if(!this._categoryNamesSetByUser)
            {
                this._categoryNames = [];
            }
            else
            {
                //ensure that all category names are strings
                this._categoryNames = getCategoryNames(value);
            }
        }
        
        /**
         * @inheritDoc
         */
        public function get clusterCount():int
        {
            return this.categoryNames.length;
        }

        /**
         * @private
         */
        private var _numLabels:Number;
        
        /**
         * @private
         */		
        private var _numLabelsSetByUser:Boolean = false;

        /**
         * @inheritDoc
         */
        public function get numLabels():Number
        {
            return _numLabels;
        }
        
        /**
         * @private (setter)
         */
        public function set numLabels(value:Number):void
        {
            if(_numLabelsSetByUser) return;
            _numLabels = value;
            _numLabelsSetByUser = true;
        }		
        
        /**
         * @private
         */
        private var _majorUnit:Number = 1; 
        
        /**
         * @private 
         * Holds value for calculateCategoryCount
         */
        private var _calculateCategoryCount:Boolean = false;
        
        /**
         * Indicates whether or not to calculate the number of categories (ticks and labels) 
         * when there is not enough room to display all labels on the axis. If set to true, the axis 
         * will determine the number of categories to plot. If not, all categories will be plotted. 
         */
        public function get calculateCategoryCount():Boolean
        {
            return _calculateCategoryCount;
        }
        
        /**
         * @private (setter)
         */
        public function set calculateCategoryCount(value:Boolean):void
        {
            _calculateCategoryCount = value;
        }
        
    //--------------------------------------
    //  Public Methods
    //--------------------------------------
        
        /**
         * @inheritDoc
         */
        public function valueToLocal(value:Object):Number
        {
            if(value === null)
            {
                return NaN;
            }
            var categoryNames:Array = this.categoryNames.concat();
            if(this.reverse)
            {
                categoryNames = categoryNames.reverse();
            }
            var index:int = categoryNames.indexOf(value.toString());
            if(index >= 0)
            {
                var position:int = this.categorySize * index + (this.categorySize / 2);
                return position;
            }
            return NaN;
        }

        /**
         * @inheritDoc
         */
        public function updateScale():void
        {
            if(!this._categoryNamesSetByUser)
            {
                this.autoDetectCategories(this.dataProvider);
            }
            this.calculateCategorySize();
        }

        /**
         * @inheritDoc
         */
        override public function getMaxLabel():String
        {
            var categoryCount:int = this.categoryNames.length;
            var maxLength:Number = 0;
            var currentLength:Number;
            var maxString:String = "x";

            for(var i:int = 0; i < categoryCount; i++)
            {
                currentLength = (this.categoryNames[i].toString()).length; 

                if(currentLength > maxLength)
                {
                    maxLength = currentLength;
                    maxString = this.categoryNames[i];
                }
            }			
            
            return this.valueToLabel(maxString) as String;	
        }

    //--------------------------------------
    //  Private Methods
    //--------------------------------------

        /**
         * @private
         * Update the labels by adding or removing some, setting the text, etc.
         */
        private function autoDetectCategories(data:Array):void
        {
            var uniqueCategoryNames:Array = [];
            var seriesCount:int = data.length;
            for(var i:int = 0; i < seriesCount; i++)
            {
                var series:ISeries = data[i] as ISeries;
                if(!series)
                {
                    continue;
                }
                
                var seriesLength:int = series.length;
                for(var j:int = 0; j < seriesLength; j++)
                {
                    var category:Object = this.chart.itemToAxisValue(series, j, this);
                    
                    //names must be unique
                    if(uniqueCategoryNames.indexOf(category) < 0)
                    {
                        uniqueCategoryNames.push(category);
                    }
                }
            }
            this._categoryNames = getCategoryNames(uniqueCategoryNames.concat());
        }
        
        /**
         * @private
         * Determines the amount of space provided to each category.
         */
        private function calculateCategorySize():void
        {
            var categoryCount:int = this.categoryNames.length;
            this.categorySize = this.renderer.length;
            if(categoryCount > 0)
            {
                this.categorySize /= categoryCount;
            }
            
            //If the number of labels will not fit on the axis or the user has specified the number of labels to
            //display, calculate the major unit. 
            var maxLabelSize:Number = (this.chart as CartesianChart).horizontalAxis == this ? this.labelData.maxLabelWidth : this.labelData.maxLabelHeight;
            if((this.categorySize < maxLabelSize && this.calculateCategoryCount) || (this._numLabelsSetByUser && this.numLabels != categoryCount))
            {
                this.calculateMajorUnit();
                (this.renderer as ICartesianAxisRenderer).majorUnitSetByUser = false;
            } 
            else
            {
                (this.renderer as ICartesianAxisRenderer).majorUnitSetByUser = true;
            }
            this.updateAxisRenderer();
        }
        
        /**
         * @private
         * Calculates which labels to skip if they will not all fit on the axis.
         */
        private function calculateMajorUnit():void
        {
            var overflow:Number = 0;
            var rotation:Number = (this.renderer as UIComponent).getStyle("labelRotation") as Number;
            var chart:CartesianChart = this.chart as CartesianChart;
            var maxLabelSize:Number;
            if(chart.horizontalAxis == this)
            {
                maxLabelSize = this.labelData.maxLabelWidth;
                if(rotation >= 0)
                {
                    if(!isNaN(this.labelData.rightLabelOffset)) overflow += this.labelData.rightLabelOffset as Number;
                }
                if(rotation <= 0)
                {
                    if(!isNaN(this.labelData.leftLabelOffset)) overflow += this.labelData.leftLabelOffset as Number;
                }			
            }
            else
            {
                maxLabelSize = this.labelData.maxLabelHeight;
                if(!isNaN(this.labelData.topLabelOffset)) overflow = this.labelData.topLabelOffset as Number;				
            }
            var labelSpacing:Number = this.labelSpacing; 
            maxLabelSize += (labelSpacing*2);
            var categoryCount:int = this.categoryNames.length;

            
            var maxNumLabels:Number = (this.renderer.length + overflow)/maxLabelSize;
            
            //If the user specified number of labels to display, attempt to show the correct number.
            if(this._numLabelsSetByUser)
            {
                maxNumLabels = Math.min(maxNumLabels, this.numLabels);
            }
            var ratio:Number = overflow/this.renderer.length;
            var overflowOffset:Number = ratio*this.categoryNames.length;
            if(isNaN(overflowOffset)) overflowOffset = 0;		
            var tempMajorUnit:Number = Math.round((this.categoryNames.length+overflowOffset)/maxNumLabels);
            this._majorUnit = tempMajorUnit;				
        }
        
        /**
         * @private 
         * Ensures all values in an array are string values
         */
        private function getCategoryNames(value:Array):Array
        {
            var names:Array = [];
            if(value != null && value.length > 0)
            {
                for(var i:int = 0; i < value.length; i++)
                {
                    names.push(value[i].toString());
                }
            }
            return names;
        }

        /**
         * @private
         */
        protected function updateAxisRenderer():void
        {
            var ticks:Array = [];
            var categoryCount:int = this.categoryNames.length,
                categoryNames = this.categoryNames.concat();
            if(this.reverse) categoryNames = categoryNames.reverse();
            var currentCat:int = 0;
            while(currentCat < categoryCount && !isNaN(categoryCount))
            {
                var category:String = categoryNames[currentCat];
                var position:Number = this.valueToLocal(category);
                var label:String = this.valueToLabel(category);
                var axisData:AxisData = new AxisData(position, category, label);
                
                if(currentCat % this._majorUnit == 0) ticks.push(axisData);
                currentCat += 1;
            }
            
            this.renderer.ticks = ticks;
            this.renderer.minorTicks = [];				
        }	
        
        /**
         * @private
         */
        override protected function parseDataProvider():void
        {
            if(!this._categoryNamesSetByUser) this.autoDetectCategories(this.dataProvider);
            var labelData:Object = getLabelData();			
            if(ICartesianAxisRenderer(this.renderer).orientation == AxisOrientation.HORIZONTAL)
            {
                labelData.leftLabelOffset /= 2;
                labelData.rightLabelOffset /= 2;
            }
            else
            {
                labelData.topLabelOffset /= 2;
                labelData.bottomLabelOffset /=2;
            }
            for(var i:String in labelData)
            {	
                this.labelData[i] = labelData[i];
            }
        }			
    }
}
